package com.wh1200.mybatis.xmlless.model

import com.wh1200.kotlin.JsonUtils
import com.wh1200.mybatis.xmlless.dialect.DialectFactory
import com.wh1200.mybatis.xmlless.enums.Operations
import com.wh1200.mybatis.xmlless.enums.Operations.*
import com.wh1200.mybatis.xmlless.model.base.BaseCriteria
import com.wh1200.mybatis.xmlless.model.criteria.CriteriaParameter
import com.wh1200.mybatis.xmlless.resolver.ColumnsResolver
import com.wh1200.mybatis.xmlless.util.getTemplate
import com.wh1200.mybatis.xmlless.util.getValueTemplate
import com.wh1200.mybatis.xmlless.util.toParameterHolder
import java.util.*


/**
 *
 * @author 吴昊
 * @date 2024/01/15
 * @version 1.0
 * @since v4.0.0
 */
class JoinOnLogicDeleteCondition(
    private val column: String,
    private val specificValue: SpecificValue
) {

  override fun toString(): String {
    return toSql()
  }

  fun toSql(): String {
    val value = resolveValue()
    return Eq.getValueTemplate().format(
        column, Eq.operator, value
    )
  }

  private fun resolveValue(): String {
    return when {
      specificValue.stringValue.isNotBlank() -> "'" + specificValue.stringValue + "'"
      else                                   -> specificValue.nonStringValue
    }
  }

}


class LogicDeleteCriteria(
    property: String,
    methodInfo: MethodInfo,
    specificValue: SpecificValue
) : QueryCriteria(
    property, Eq, listOf(), methodInfo, specificValue, ConditionConnector.AND
)

/**
 *
 * @author 吴昊
 * @since 0.0.1
 */
open class QueryCriteria(
    private val property: String,
    private val operator: Operations,
    parameters: List<CriteriaParameter>,
    methodInfo: MethodInfo,
    private val specificValue: SpecificValue? = null,
    append: ConditionConnector = ConditionConnector.AND
) : BaseCriteria(parameters, methodInfo, append) {

  companion object {

    /**  foreach模板 */
    const val FOREACH = """<foreach collection="%s" item="%s" separator="%s" open="(" close=")">
  %s
</foreach>"""

    @Suppress("unused")
    @JvmStatic
    fun toJson(obj: Any?): String? {
      return JsonUtils.toJson(obj)
    }
  }

  /** 选中的列 */
  override fun getColumns(extraJoins: List<JoinInfo>): List<SelectColumn> {
    val columns = ColumnsResolver.resolveColumnByPropertyName(
        property, methodInfo, forHaving
    ).map {
      if (forHaving && methodInfo.propertiesMapping[property] != null) {
        val mapping = methodInfo.findMapping(property) ?: error("无法找到${property}的映射")
        SelectColumn(mapping.fieldWrapper, null, methodInfo.propertiesMapping[property]!!, null)
      } else {
        it
      }
    }
    return columns
  }

  override fun toSqlWithoutTest(): String {
    val mapping = methodInfo.findMapping(property)
    val parentMapping = getParentMapping()
    val columnResult = getColumns().joinToString(",\n\t") { it.toSqlWithoutAlias() }
    val value = resolveValue()

    return when {

      value != null && operator == Contains                                         -> DialectFactory.getDialect(methodInfo.dbType)
          .createJsonArrayElementIn(columnResult, value, value = true)

      // 条件变量为确定的值时
      value != null                                                -> operator.getValueTemplate().format(
          columnResult, operator.operator, value
      ) + " " + append

      (mapping != null && mapping.isJsonArray)
          || (parentMapping != null && parentMapping.isJsonObject) -> buildJsonQuery(
          columnResult, operator
      ) + " " + append

      operator == NotIn
          || operator == In
          && mapping?.joinInfo is PropertyJoinInfo                 -> operator.getTemplate().format(
          methodInfo.mappings.tableName + "." + methodInfo.mappings.tableInfo.keyColumn,
          operator.operator,
          *scriptParams(mapping).toTypedArray()
      ) + " " + append


      else                                                         -> {
        operator.getTemplate().format(
            columnResult, operator.operator, *scriptParams().toTypedArray()
        ) + " " + append
      }
    }
  }

  override fun getTableNames(): List<String> {
    val cols = this.getColumns()
    val allTables = cols.map { it.middleTables }.flatten() + cols.mapNotNull { it.table }
    return allTables.map { it.getAliasOrName() }.distinct()
  }

  override fun toString(): String {
    return operator.getTemplate().format(
        property, operator.operator, *scriptParams().toTypedArray()
    )
  }

  override fun getProperty(): String {
    return property
  }

  override fun toSql(): String {
    return toSql(null, "if")
  }

  override fun toSql(parameterTest: TestInfo?, ifTag: String): String {
    return wrapWithTests(toSqlWithoutTest(), parameterTest, ifTag)
  }

  override fun clone(parameters: List<CriteriaParameter>): BaseCriteria {
    return QueryCriteria(
        property,
        operator,
        parameters,
        methodInfo,
        specificValue,
        append
    )
  }

  override fun clone(append: ConditionConnector): BaseCriteria {
    return QueryCriteria(
        property,
        operator,
        parameters,
        methodInfo,
        specificValue,
        append
    )
  }

  private fun getParentMapping(): FieldMapping? {
    if (property.contains(".")) {
      return methodInfo.findMapping(property.split(".")[0])
    }
    return null
  }

  private fun buildJsonQuery(
      columnResult: String, operator: Operations
  ): String {
    when (operator) {
      Contains, Eq, EqDefault    -> {
        return DialectFactory.getDialect(methodInfo.dbType)
            .createJsonArrayElementEqual(columnResult, scriptParams(wrap = false).first())
      }

      ContainsAny                -> {
        return DialectFactory.getDialect(methodInfo.dbType)
            .createJsonArrayElementIn(columnResult, scriptParams(wrap = false).first())
      }

      In, NotIn                  -> {
        return FOREACH.format(
            realParams()[0],
            "item",
            " OR ",
            DialectFactory.getDialect(methodInfo.dbType).createJsonArrayElementEqual(
                columnResult, "item"
            )
        )
      }

      IsNotNull, IsNull, NotNull -> {
        return realParams()[0].toString() + " " + operator.operator
      }

      else                       -> throw IllegalStateException("暂不支持${operator.operator}的json查询")
    }
  }

  private fun getOnlyParameterName(): String? {
    return parameters.firstOrNull()?.name?.toString()
  }

  private fun resolveValue(): String? {
    val paramName = getOnlyParameterName()
    return when {
      specificValue != null -> when {
        specificValue.stringValue.isNotBlank() -> "'" + specificValue.stringValue + "'"
        else                                   -> specificValue.nonStringValue
      }

      paramName != null     -> when {
        paramName.matches("\\d+".toRegex()) -> paramName
        paramName == "true"                 -> paramName.uppercase(Locale.getDefault())
        else                                -> null
      }

      else                  -> null
    }
  }

  private fun scriptParams(
      propertyMapping: FieldMapping? = null,
      wrap: Boolean = true
  ): List<String> {
    val realParams = realParams()
    // 如果存在realParam未 xxInXx的情况
    if (operator in listOf(In, NotIn)) {
      if (propertyMapping != null && propertyMapping.joinInfo is PropertyJoinInfo) {
        val selectedColumn = propertyMapping.joinInfo.targetExpression.toString()
        return listOf(
            "(SELECT $selectedColumn FROM ${propertyMapping.joinInfo.joinTable.name} WHERE " + "${
              propertyMapping
                  .joinInfo.propertyColumn.name
            } = ${toParameterHolder(realParams[0].toString())})"
        )
      }
      return listOf(FOREACH.format(realParams[0], "item", ", ", "#{item}"))
    }
    return realParams.map {
      it.toExpression(wrap)
    }
  }

}

/**
 * @author 吴昊
 * @date 2023/12/11
 * @version 1.0
 * @since v4.0.0
 */
class ParamHolder(val name: String?, val value: String?) {

  override fun toString(): String {
    return name ?: value ?: ""
  }

  fun toExpression(wrap: Boolean): String {
    return if (wrap && name != null) {
      toParameterHolder(name)
    } else {
      name ?: value ?: ""
    }
  }

}

/**
 * 条件连接后缀类型
 *
 * @author 吴昊
 * @date 2023/12/07
 * @version 1.0
 * @since v4.0.0
 */
enum class ConditionConnector {

  AND,
  OR;

}
